Appendix E: External Automation Setup
This appendix provides the reusable EAConnect helper class plus the configuration details required to build external automation tools against Enterprise Architect (EA). It is intended as the “plumbing” layer behind the example in Chapter 6 – External Automation.
E.1 Purpose
The EAConnect helper abstracts the complexities of EA’s COM interface and standardises safety practices:
Attaching to or launching EA consistently
Waiting for the Repository to become available
Opening models automatically if needed
Handling bitness and Interop.EA.dll
Providing safe wrappers for common tasks (find package, add element, add tagged value)
Enforcing dry-run by default
Writing CSV audit logs with auto-derived filenames
Refreshing EA’s UI at sensible points
With this helper, your utilities focus on intent rather than boilerplate.
E.2 Usage
A minimal program using the helper:
using System;
using EA;
using EA.Automation; // the helper namespace
class Program
{
\[STAThread\]
static void Main(string\[\] args)
{
using var ea = EAConnect.AttachOrLaunch(new EAConnect.Options { ShowUI =
true });
Package pkg = ea.GetSelectedPackageOrRoot();
var result = EAConnect.AddElement(ea.Repo, pkg, "DemoClass", "Class",
"Created by EAConnect.", dryRun: true);
Console.WriteLine(result.Summary);
}
}This pattern keeps the Program.cs short and declarative. All detailed logic resides in the helper.
E.3 The EAConnect Helper Class
//
=======================================================================
// File: EAConnect.cs
// Project: EA Automation Helper
// Author: \<Your Name\>
// Created: 2025-08-31
// Last Update: 2025-08-31
//
// PURPOSE
// -------
// Provides a safe, reusable abstraction over EA’s COM automation API.
// Handles: attach/launch, repository polling, model open, package
// resolution,
// element creation, tagged value creation, CSV logging, and UI refresh.
//
// USAGE
// -----
// using var ea = EAConnect.AttachOrLaunch(new EAConnect.Options {
// ShowUI = true });
// var pkg = ea.GetSelectedPackageOrRoot();
// var result = EAConnect.AddElement(ea.Repo, pkg, "NewClass", "Class",
// "Notes", true);
// Console.WriteLine(result.Summary);
//
// ASSUMPTIONS
// -----------
// - EA is installed and COM-registered (ProgID = "EA.App").
// - Interop.EA.dll referenced (from EA install folder).
// - Bitness of project matches EA (x64 for EA 64-bit; x86 for 32-bit).
// - .NET project marked \[STAThread\] on entry point.
//
// SAFETY
// ------
// - Dry-run enabled by default in sample programs.
// - CSV audit logging for all writes.
// - UI refresh at batch boundaries only.
//
// DEPENDENCIES
// ------------
// - System.Runtime.InteropServices (Marshal.GetActiveObject).
// - Interop.EA.dll.
//
// UPDATE HISTORY
// --------------
// - 2025-08-31: Initial helper version.
//
=======================================================================
using System;
using System.IO;
using System.Runtime.InteropServices;
using EA;
namespace EA.Automation
{
public static class EAConnect
{
// Encapsulates a connected EA session
public sealed class Session : IDisposable
{
public EA.App App { get; }
public EA.Repository Repo { get; }
internal Session(EA.App app, EA.Repository repo)
{
App = app;
Repo = repo;
}
public Package GetSelectedPackageOrRoot()
{
Package? pkg = Repo.GetTreeSelectedPackage();
if (pkg != null) return pkg;
if (Repo.Models.Count \> 0) return
> (Package)Repo.Models.GetAt(0);
throw new InvalidOperationException(
> "No package available in repository.");
}
public void Dispose()
{
// Release COM references cleanly
Marshal.ReleaseComObject(Repo);
Marshal.ReleaseComObject(App);
}
}
// Options for attaching or launching EA
public sealed class Options
{
public bool PreferAttach { get; set; } = true;
public bool LaunchIfNotRunning { get; set; } = true;
public string? ModelPath { get; set; }
public bool ShowUI { get; set; } = true;
public int StartupWaitMs { get; set; } = 1500;
public int RepoPollMs { get; set; } = 100;
public int RepoPollMax { get; set; } = 50;
}
// Attach to EA or launch new instance
public static Session AttachOrLaunch(Options? options = null)
{
options ??= new Options();
EA.App? app = null;
if (options.PreferAttach)
{
try { app = (EA.App)Marshal.GetActiveObject("EA.App"); }
catch { /\* none running \*/ }
}
if (app == null && options.LaunchIfNotRunning)
{
> Type t = Type.GetTypeFromProgID("EA.App") ?? throw new
>
> InvalidOperationException("EA not installed.");
app = (EA.App)Activator.CreateInstance(t)!;
if (!options.ShowUI) app.Visible = false;
System.Threading.Thread.Sleep(options.StartupWaitMs);
}
if (app == null) throw new InvalidOperationException(
> "Unable to connect to EA.");
EA.Repository repo = app.Repository;
// Poll until repository becomes available
int polls = 0;
while (repo == null && polls \< options.RepoPollMax)
{
System.Threading.Thread.Sleep(options.RepoPollMs);
repo = app.Repository;
polls++;
}
if (repo == null) throw new InvalidOperationException("
> EA.Repository did not initialise.");
if (!string.IsNullOrWhiteSpace(options.ModelPath) &&
> string.IsNullOrWhiteSpace(repo.ConnectionString))
repo.OpenFile(options.ModelPath);
return new Session(app, repo);
}
// Result container
public sealed class Result
{
public int UpdatedCount { get; init; }
public string Summary { get; init; } = "";
public string? LogPath { get; init; }
public string? ElementGuid { get; init; }
}
// Add a new element safely
public static Result AddElement(Repository repo,
> Package targetPackage,
>
> string name,
>
> string type,
>
> string? notes, bool dryRun = true, CsvLogger? logger = null)
{
var el = (Element)targetPackage.Elements.AddNew(name, type);
if (!string.IsNullOrWhiteSpace(notes)) el.Notes = notes;
if (dryRun)
{
logger?.WriteRow(DateTime.Now, "DRYRUN",
> targetPackage.PackageID,
>
> "", name, type, notes ?? "");
return new Result { UpdatedCount = 0,
> Summary = \$"\[DRYRUN\] Would create: {name} ({type})" };
}
if (!el.Update())
throw new InvalidOperationException(
> "Element.Update() failed.");
repo.RefreshModelView(targetPackage.PackageID);
logger?.WriteRow(DateTime.Now, "CREATE",
> targetPackage.PackageID,
>
> el.ElementGUID, name, type, notes ?? "");
return new Result
{
UpdatedCount = 1,
Summary = \$"Created element: {name} ({type})",
ElementGuid = el.ElementGUID,
LogPath = logger?.Path
};
}
// CSV logger
public sealed class CsvLogger : IDisposable
{
private readonly StreamWriter \_writer;
public string Path { get; }
private CsvLogger(string path)
{
Path = path;
\_writer = new StreamWriter(path, append: false,
> System.Text.Encoding.UTF8);
}
public static CsvLogger Open(string path) =\>
> new CsvLogger(path);
public void WriteHeader(string header) =\>
\_writer.WriteLine(header);
public void WriteRow(params object?\[\] cols)
=\> \_writer.WriteLine(string.Join(",",
> Array.ConvertAll(cols, c =\> c?.ToString() ?? "")));
public void Close() =\> \_writer.Flush();
public void Dispose() =\> \_writer.Dispose();
}
}
}E.4 Configuration & Setup
Bitness
EA 16+ is usually 64-bit.
Match your project’s <PlatformTarget> to EA’s bitness (x64 or x86).
Interop.EA.dll
Found in EA install folder.
Add as a reference in your .NET project.
STAThread
- COM automation requires [STAThread] on your entry point.
Model Files
Use .qea / .qeax / .eapx.
Options.ModelPath can auto-open a file if none is open.
Dry Run & Logging
Always start in dryRun = true.
Review the generated CSV log before committing real updates.
Distribution
Package as a console utility or integrate into CI/CD.
Keep EAConnect.cs in a shared library for consistency.